package com.quick.develop.flowable.service.task.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import com.quick.develop.flowable.constant.BpmConstants;
import com.quick.develop.flowable.domain.dto.task.BpmTaskDTO;
import com.quick.develop.flowable.domain.vo.system.SysUserDeptVO;
import com.quick.develop.flowable.domain.vo.task.BpmTaskReqVO;
import com.quick.develop.framework.common.page.PageResult;
import com.quick.develop.framework.utils.PageUtils;
import com.quick.develop.framework.utils.StringUtils;
import com.quick.develop.framework.security.utils.SecurityUtils;
import com.quick.develop.flowable.domain.task.*;
import com.quick.develop.flowable.framework.convert.instance.BpmProcessInstanceConvert;
import com.quick.develop.flowable.framework.convert.task.BpmTaskConvert;
import com.quick.develop.flowable.framework.factory.FlowServiceFactory;
import com.quick.develop.flowable.utils.FindNextNodeUtil;
import com.quick.develop.flowable.utils.FlowableUtil;
import com.quick.develop.flowable.mapper.BpmHistoricTaskMapper;
import com.quick.develop.flowable.mapper.BpmTaskMapper;
import com.quick.develop.flowable.service.definition.IBpmProcessDefinitionService;
import com.quick.develop.flowable.service.process.IBpmProcessInstanceService;
import com.quick.develop.flowable.service.task.IBpmCommentService;
import com.quick.develop.flowable.service.task.IBpmTaskService;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Comment;
import org.flowable.identitylink.service.impl.persistence.entity.HistoricIdentityLinkEntityImpl;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

import static com.quick.develop.flowable.enums.BpmErrorCodeConstants.PROCESS_INSTANCE_ID_EMPTY;
import static com.quick.develop.flowable.enums.BpmErrorCodeConstants.TASK_EXECUTION_IS_EMPTY;
import static com.quick.develop.framework.common.utils.ServiceExceptionUtils.exception;
import static com.quick.develop.framework.utils.collection.CollectionUtils.*;

/**
 * @author junyuan.liu
 * @description:
 * @date 2022/4/25 16:23
 */
@Service
@Slf4j
public class BpmTaskServiceImpl extends FlowServiceFactory implements IBpmTaskService {
    @Resource
    private IBpmProcessDefinitionService bpmProcessDefinitionService;

    @Resource
    private IBpmProcessInstanceService bpmProcessInstanceService;

    @Resource
    private IBpmCommentService bpmCommentService;

    @Resource
    private BpmHistoricTaskMapper bpmHistoricTaskMapper;

    @Resource
    private BpmTaskMapper bpmTaskMapper;

    @Override
    public Task getTaskByProcessInstanceId(String processInstanceId) {
        Optional.ofNullable(processInstanceId).orElseThrow(() -> exception(PROCESS_INSTANCE_ID_EMPTY));

        return taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult();
    }

    @Override
    public List<Task> getTasksByProcessInstanceId(String processInstanceId) {
        Optional.ofNullable(processInstanceId)
                .orElseThrow(() -> exception(PROCESS_INSTANCE_ID_EMPTY));

        return taskService.createTaskQuery().processInstanceId(processInstanceId).list();
    }

    @Override
    public List<HistoricTaskInstance> getTasksFinishedByProcessInstanceId(String processInstanceId) {
        return historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId).finished()
                .orderByHistoricTaskInstanceEndTime().desc().list();
    }

    @Override
    public List<Task> getTasksByProcessInstanceIds(Collection<String> processInstanceIds) {
        if (CollUtil.isEmpty(processInstanceIds)) {
            return Collections.emptyList();
        }
        return taskService.createTaskQuery().processInstanceIdIn(processInstanceIds).list();
    }

    @Override
    public Task getTaskById(String id) {
        return taskService.createTaskQuery().taskId(id).singleResult();
    }

    @Override
    public Task getTaskByExecutionId(String id) {
        return taskService.createTaskQuery().executionId(id).singleResult();
    }

    @Override
    public HistoricTaskInstance getHistTaskById(String id) {
        if (StringUtils.isEmpty(id)) {
            return null;
        }
        return historyService.createHistoricTaskInstanceQuery()
                .taskId(id).singleResult();
    }


    @Override
    public List<UserTask> findReturnTaskList(BpmTaskReqVO taskVo) {
        // 当前任务 task
        Task task = taskService.createTaskQuery().taskId(taskVo.getTaskId()).singleResult();
        // 获取流程定义信息
        ProcessDefinition processDefinition = repositoryService
                .createProcessDefinitionQuery()
                .processDefinitionId(task.getProcessDefinitionId()).singleResult();
        // 获取所有节点信息，暂不考虑子流程情况
        Process process = repositoryService.getBpmnModel(processDefinition.getId()).getProcesses().get(0);
        Collection<FlowElement> flowElements = process.getFlowElements();
        // 获取当前任务节点元素
        UserTask source = null;
        if (CollUtil.isNotEmpty(flowElements)) {
            source = (UserTask) flowElements.stream()
                    .filter(e -> e.getId().equals(task.getTaskDefinitionKey()))
                    .findFirst().orElse(null);
        }
        // 获取节点的所有路线
        List<List<UserTask>> roads = FlowableUtil.findRoad(source, null, null, null);
        // 可回退的节点列表
        List<UserTask> userTaskList = new ArrayList<>();
        for (List<UserTask> road : roads) {
            if (userTaskList.size() == 0) {
                // 还没有可回退节点直接添加
                userTaskList = road;
            } else {
                // 如果已有回退节点，则比对取交集部分
                userTaskList.retainAll(road);
            }
        }

        return userTaskList;
    }


    @Override
    public PageResult<BpmTaskTodoPageItemRespVO> getTodoTaskPage(BpmTaskTodoPageReqVO pageVO) {
        String userId = SecurityUtils.getUserCode();
        // 查询待办任务
        TaskQuery taskQuery = taskService.createTaskQuery()
                .includeProcessVariables()
                .orderByTaskName()
                .orderByTaskCreateTime().desc(); // 创建时间倒序
        if (StringUtils.isNotEmpty(pageVO.getOption()) && BpmConstants.CANDIDATE.equals(pageVO.getOption())) {
            taskQuery.taskCandidateUser(userId);
        } else {
            taskQuery.taskCandidateOrAssigned(userId);
        }

        Optional.ofNullable(pageVO.getProcessCategories()).ifPresent(taskQuery::processCategoryIn);
        Optional.ofNullable(pageVO.getName())
                .ifPresent(name -> taskQuery.taskNameLike("%" + name + "%"));
        Optional.ofNullable(pageVO.getBeginCreateTime())
                .ifPresent(taskQuery::taskCreatedAfter);
        Optional.ofNullable(pageVO.getEndCreateTime())
                .ifPresent(taskQuery::taskCreatedBefore);
        Optional.ofNullable(pageVO.getProcessDefinitionName())
                .ifPresent(n -> taskQuery.processDefinitionNameLike("%" + n + "%"));

        Optional.ofNullable(pageVO.getStartUser())
                .ifPresent(startUser ->
                        taskQuery.processVariableValueLikeIgnoreCase(BpmConstants.INITIATOR, "%" + startUser + "%"));

        if (StringUtils.isNotEmpty(pageVO.getProcessName())) {
            taskQuery.or()
                    .processVariableValueLikeIgnoreCase(BpmConstants.PROCESS_NAME, "%" + pageVO.getProcessName() + "%")
                    .processVariableValueLikeIgnoreCase(BpmConstants.BUSINESS_CODE, "%" + pageVO.getProcessName() + "%")
                    .endOr();
        }


        // 执行查询
        List<Task> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
        if (CollUtil.isEmpty(tasks)) {
            return PageResult.empty(taskQuery.count());
        }
        List<ProcessInstance> processInstanceList = bpmProcessInstanceService.getProcessInstanceList(convertSet(tasks, Task::getProcessInstanceId));
        // 获得 ProcessInstance Map
        Map<String, ProcessInstance> processInstanceMap = convertMap(processInstanceList, ProcessInstance::getProcessInstanceId);

        // TODO  获取发起人部门
        List<SysUserDeptVO> userDeptList = this.getUserDept(BpmProcessInstanceConvert.INSTANCE.convertStartUserList(processInstanceList));

        // 获取 ProcessDefinition
        Map<String, ProcessDefinition> processDefinitionMap = bpmProcessDefinitionService.getProcessDefinitionMap(convertSet(tasks, Task::getProcessDefinitionId));


        return new PageResult<>(BpmTaskConvert.INSTANCE.convertList1(tasks, processInstanceMap,
                processDefinitionMap,
                userDeptList),
                taskQuery.count());
    }

    @Override
    public long getTodoCount() {
        return taskService.createTaskQuery().taskCandidateOrAssigned(SecurityUtils.getUserCode()).count();
    }

    @Override
    public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage(BpmTaskDonePageReqVO pageVO) {
        String userId = SecurityUtils.getUserCode();
        // 查询已办任务
        HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery()
                .finished() // 已完成
                .taskAssignee(userId)
                .includeProcessVariables()
                .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序

        Optional.ofNullable(pageVO.getProcessCategories()).ifPresent(taskQuery::processCategoryIn);
        Optional.ofNullable(pageVO.getName())
                .ifPresent(name -> taskQuery.taskNameLike("%" + name + "%"));
        Optional.ofNullable(pageVO.getProcessDefinitionName())
                .ifPresent(n -> taskQuery.processDefinitionNameLike("%" + n + "%"));
        Optional.ofNullable(pageVO.getBeginCreateTime()).ifPresent(taskQuery::taskCreatedAfter);
        Optional.ofNullable(pageVO.getEndCreateTime()).ifPresent(taskQuery::taskCreatedBefore);
        Optional.ofNullable(pageVO.getBeginEndTime()).ifPresent(taskQuery::taskCompletedAfter);
        Optional.ofNullable(pageVO.getEndEndTime()).ifPresent(taskQuery::taskCompletedBefore);
        if (StringUtils.isNotEmpty(pageVO.getProcessName())) {
            taskQuery.or()
                    .processVariableValueLikeIgnoreCase(BpmConstants.PROCESS_NAME, "%" + pageVO.getProcessName() + "%")
                    .processVariableValueLikeIgnoreCase(BpmConstants.BUSINESS_CODE, "%" + pageVO.getProcessName() + "%")
                    .endOr();
        }
        // 执行查询
        List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());

        if (CollUtil.isEmpty(tasks)) {
            return PageResult.empty(taskQuery.count());
        }

        // 获得 ProcessInstance Map
        Map<String, HistoricProcessInstance> historicProcessInstanceMap = bpmProcessInstanceService.getHistoricProcessInstanceMap(
                convertSet(tasks, HistoricTaskInstance::getProcessInstanceId));
//        Map<String, HistoricProcessInstance> historicProcessInstanceMap = new HashMap<>();

        // 获取 ProcessDefinition
//        Map<String, ProcessDefinition> processDefinitionMap = bpmProcessDefinitionService.getProcessDefinitionMap(convertSet(tasks, HistoricTaskInstance::getProcessDefinitionId));
        Map<String, ProcessDefinition> processDefinitionMap = new HashMap<>();


        //审批意见
        Map<String, List<Comment>> comments = bpmCommentService.getTaskCommentsByTaskIds(convertSet(tasks, HistoricTaskInstance::getId));


        return new PageResult<>(
                BpmTaskConvert.INSTANCE.convertList2(tasks, historicProcessInstanceMap, processDefinitionMap, comments)
                , taskQuery.count());
    }

    @Override
    public PageResult<BpmTaskDonePageItemRespVO> getDoneTaskPage2(BpmTaskDonePageReqVO pageVO) {
        String userId = SecurityUtils.getUserCode();
        // 查询已办任务
        HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery()
                .finished() // 已完成
                .taskAssignee(userId)
                .includeProcessVariables()
                .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序

        Optional.ofNullable(pageVO.getName())
                .ifPresent(name -> taskQuery.taskNameLike("%" + name + "%"));
        Optional.ofNullable(pageVO.getProcessDefinitionName())
                .ifPresent(n -> taskQuery.processDefinitionNameLike("%" + n + "%"));
        Optional.ofNullable(pageVO.getBeginCreateTime()).ifPresent(taskQuery::taskCreatedAfter);
        Optional.ofNullable(pageVO.getEndCreateTime()).ifPresent(taskQuery::taskCreatedBefore);
        Optional.ofNullable(pageVO.getBeginEndTime()).ifPresent(taskQuery::taskCompletedAfter);
        Optional.ofNullable(pageVO.getEndEndTime()).ifPresent(taskQuery::taskCompletedBefore);

        if (StringUtils.isNotEmpty(pageVO.getProcessCategory())) {
            taskQuery.subScopeId(pageVO.getProcessCategory());
        }

        if (StringUtils.isNotEmpty(pageVO.getProcessName())) {
            taskQuery.or()
                    .processVariableValueLikeIgnoreCase(BpmConstants.PROCESS_NAME, "%" + pageVO.getProcessName() + "%")
                    .processVariableValueLikeIgnoreCase(BpmConstants.BUSINESS_CODE, "%" + pageVO.getProcessName() + "%")
                    .endOr();
        }

        // 执行查询
        List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());

        if (CollUtil.isEmpty(tasks)) {
            return PageResult.empty(taskQuery.count());
        }

        return new PageResult<>(BpmTaskConvert.INSTANCE.convertList2(tasks), taskQuery.count());
    }

    @Override
    public long getDoneCount() {
        return historyService.createHistoricTaskInstanceQuery()
                .finished() // 已完成
                .taskAssignee(SecurityUtils.getUserCode()).count();
    }

    @Override
    public void assign(String id, String assignee) {
        taskService.setAssignee(id, assignee);
    }


    @Override
    public List<BpmTaskRespVO> getTaskListByProcessInstanceId(String processInstanceId) {
        // 获得任务列表
        List<HistoricTaskInstance> taskList = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .orderByHistoricTaskInstanceStartTime().desc()
                .list();
        if (CollUtil.isEmpty(taskList)) {
            return Collections.emptyList();
        }
//        HistoricProcessInstance hisInst = bpmProcessInstanceService.getHistoricProcessInstance(processInstanceId);

        Map<String, List<Comment>> comments = bpmCommentService.getTaskCommentMapByProcessInstanceId(processInstanceId);

        return BpmTaskConvert.INSTANCE.convertList3(taskList, comments);
    }

    @Override
    public List<Task> getTaskListByBusinessKey(String businessKey) {
        return Optional.ofNullable(businessKey)
                .map(k -> taskService.createTaskQuery().processInstanceBusinessKey(k)
                        .orderByTaskCreateTime().asc()
                        .list())
                .orElse(Collections.emptyList());
//        return taskService.createTaskQuery().processInstanceBusinessKey(businessKey).list();
    }

    @Override
    public List<BpmTaskDTO> getRuTaskListByBusinessKey(String businessKey) {
        return BpmTaskConvert.INSTANCE.convertList5(this.getTaskListByBusinessKey(businessKey));
    }

    @Override
    public List<UserTask> getNextTaskList(BpmTaskReqVO reqVO) {
        BpmnModel bpmnModel = bpmProcessDefinitionService.getBpmnModelByDefinitionId(reqVO.getProcessDefinitionId());
        return FindNextNodeUtil.getNextUserTasks(bpmnModel, reqVO.getTaskDefinitionKey(), reqVO.getProcessVariables());
    }

    @Override
    public String getSingleBackAssignee(String processInstanceId, String taskDefKey) {
        List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery()
                .processInstanceId(processInstanceId)
                .taskDefinitionKey(taskDefKey)
                .finished()
                .orderByHistoricTaskInstanceEndTime()
                .desc()
                .list();

        return (CollUtil.isNotEmpty(list) && list.size() > 0) ? list.get(0).getAssignee() : null;
    }

    @Override
    public Map<String, Set<String>> getCandidateGroups(Collection<String> ids) {
        if (ids.isEmpty()) {
            return MapUtil.empty();
        }

        List<HistoricTaskInstance> tasks = bpmHistoricTaskMapper.selectCandidateHistoricTasksByIds(ids);

        List<HistoricIdentityLinkEntityImpl> list = new ArrayList<>();
        for (HistoricTaskInstance task : tasks) {
            Optional.ofNullable(task.getIdentityLinks())
                    .ifPresent(i -> list.addAll((List<HistoricIdentityLinkEntityImpl>) i));
        }


        return convertMultiMap2(list, HistoricIdentityLinkEntityImpl::getTaskId, HistoricIdentityLinkEntityImpl::getGroupId);
    }

    @Override
    public Boolean isTaskMulti(String taskId) {
        Task task = getTaskById(taskId);
        Optional.ofNullable(task).orElseThrow(() -> exception(TASK_EXECUTION_IS_EMPTY));
        BpmnModel bpmnModel = bpmProcessDefinitionService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());

        return FlowableUtil.isTaskMulti(bpmnModel, task.getTaskDefinitionKey());
    }

    @Async
    @Override
    public void resetTaskCreateTime(String id) {
        Date date = new Date();
        bpmTaskMapper.resetTaskCreateTime(id, date);
    }

    @Override
    public Object getVariableLocal(String taskId, String variableName) {
        return taskService.getVariableLocal(taskId, variableName);
    }

    @Override
    public List<SysUserDeptVO> getUserDept(List<SysUserDeptVO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        // TODO 转换发起部门列表
        return Collections.emptyList();
    }

}
